import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

/// 控制器
class ZoomSliverBarController extends StatefulWidget {
  final Widget child;

  ZoomSliverBarController({
    required this.child,
  });

  @override
  _ZoomSliverBarControllerState createState() =>
      _ZoomSliverBarControllerState();
}

class _ZoomSliverBarControllerState extends State<ZoomSliverBarController> {
  List<Future<void> Function()> _stopRefreshs = [];

  _pushStopRefreshs(Future<void> Function() f) {
    setState(() {
      _stopRefreshs.add(f);
    });
  }

  bool Function(ScrollNotification) _onNotification = (ScrollNotification s) {
    return true;
  };

  _setOnNotification(bool Function(ScrollNotification) f) {
    setState(() {
      _onNotification = f;
    });
  }

  List<Future<void> Function()> _startRefreshs = [];

  _pushStartRefreshs(Future<void> Function() f) {
    setState(() {
      _startRefreshs.add(f);
    });
  }

  @override
  Widget build(BuildContext context) {
    return _ZoomSliverBarStatus(
      onNotification: _onNotification,
      setOnNotification: _setOnNotification,
      startRefreshs: _startRefreshs,
      pushStartRefreshs: _pushStartRefreshs,
      stopRefreshs: _stopRefreshs,
      pushStopRefreshs: _pushStopRefreshs,
      child: ZoomSliverBarListener(
        child: widget.child,
      ),
    );
  }
}

/// 状态管理
// ignore: must_be_immutable
class _ZoomSliverBarStatus extends InheritedWidget {
  bool Function(ScrollNotification) onNotification;
  Function(bool Function(ScrollNotification)) setOnNotification;
  List<Future<void> Function()> startRefreshs;
  Function(Future<void> Function()) pushStartRefreshs;
  List<Future<void> Function()> stopRefreshs;
  Function(Future<void> Function()) pushStopRefreshs;

  _ZoomSliverBarStatus({
    Key? key,
    required Widget child,
    required this.onNotification,
    required this.setOnNotification,
    required this.startRefreshs,
    required this.pushStartRefreshs,
    required this.stopRefreshs,
    required this.pushStopRefreshs,
  }) : super(
          key: key,
          child: child,
        );

  static _ZoomSliverBarStatus? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<_ZoomSliverBarStatus>();
  }

  //是否重建widget就取决于数据是否相同
  @override
  bool updateShouldNotify(_ZoomSliverBarStatus oldWidget) {
    return onNotification != oldWidget.onNotification;
  }
}

/// 监听
class ZoomSliverBarListener extends StatefulWidget {
  final Widget child;

  ZoomSliverBarListener({
    required this.child,
  });

  @override
  _ZoomSliverBarListenerState createState() => _ZoomSliverBarListenerState();
}

class _ZoomSliverBarListenerState extends State<ZoomSliverBarListener> {
  @override
  Widget build(BuildContext context) {
    return NotificationListener<ScrollNotification>(
      onNotification: _ZoomSliverBarStatus.of(context)?.onNotification,
      child: widget.child,
    );
  }
}

/// SliverBar
class ZoomSliverBar extends StatefulWidget {
  const ZoomSliverBar({
    Key? key,
    this.distance = 100,
    this.triggerDistance = 70,
    this.onRefresh,
    this.leading,
    this.automaticallyImplyLeading = true,
    this.title,
    this.actions,
    this.flexibleSpace,
    this.bottom,
    this.elevation,
    this.shadowColor,
    this.forceElevated = false,
    this.backgroundColor,
    this.foregroundColor,
    this.brightness,
    this.iconTheme,
    this.actionsIconTheme,
    this.textTheme,
    this.primary = true,
    this.centerTitle,
    this.excludeHeaderSemantics = false,
    this.titleSpacing,
    this.collapsedHeight,
    this.expandedHeight,
    this.floating = false,
    this.pinned = false,
    this.snap = false,
    this.stretch = false,
    this.stretchTriggerOffset = 100.0,
    this.onStretchTrigger,
    this.shape,
    this.toolbarHeight = kToolbarHeight,
    this.leadingWidth,
    this.backwardsCompatibility = true,
    this.toolbarTextStyle,
    this.titleTextStyle,
    this.systemOverlayStyle,
  })  : assert(automaticallyImplyLeading != null),
        assert(forceElevated != null),
        assert(primary != null),
        assert(floating != null),
        assert(pinned != null),
        assert(snap != null),
        assert(stretch != null),
        assert(toolbarHeight != null),
        assert(floating || !snap,
            'The "snap" argument only makes sense for floating app bars.'),
        assert(stretchTriggerOffset > 0.0),
        assert(collapsedHeight == null || collapsedHeight >= toolbarHeight,
            'The "collapsedHeight" argument has to be larger than or equal to [toolbarHeight].'),
        super(key: key);

  /// 可拉动的距离
  final double distance;

  /// 触发事件距离
  final double triggerDistance;

  /// 触发事件
  final Future<void> Function()? onRefresh;

  /// {@macro flutter.material.appbar.leading}
  ///
  /// This property is used to configure an [AppBar].
  final Widget? leading;

  /// {@macro flutter.material.appbar.automaticallyImplyLeading}
  ///
  /// This property is used to configure an [AppBar].
  final bool automaticallyImplyLeading;

  /// {@macro flutter.material.appbar.title}
  ///
  /// This property is used to configure an [AppBar].
  final Widget? title;

  /// {@macro flutter.material.appbar.actions}
  ///
  /// This property is used to configure an [AppBar].
  final List<Widget>? actions;

  /// {@macro flutter.material.appbar.flexibleSpace}
  ///
  /// This property is used to configure an [AppBar].
  final Widget? flexibleSpace;

  /// {@macro flutter.material.appbar.bottom}
  ///
  /// This property is used to configure an [AppBar].
  final PreferredSizeWidget? bottom;

  /// {@macro flutter.material.appbar.elevation}
  ///
  /// This property is used to configure an [AppBar].
  final double? elevation;

  /// {@macro flutter.material.appbar.shadowColor}
  ///
  /// This property is used to configure an [AppBar].
  final Color? shadowColor;

  /// Whether to show the shadow appropriate for the [elevation] even if the
  /// content is not scrolled under the [AppBar].
  ///
  /// Defaults to false, meaning that the [elevation] is only applied when the
  /// [AppBar] is being displayed over content that is scrolled under it.
  ///
  /// When set to true, the [elevation] is applied regardless.
  ///
  /// Ignored when [elevation] is zero.
  final bool forceElevated;

  /// {@macro flutter.material.appbar.backgroundColor}
  ///
  /// This property is used to configure an [AppBar].
  final Color? backgroundColor;

  /// {@macro flutter.material.appbar.foregroundColor}
  ///
  /// This property is used to configure an [AppBar].
  final Color? foregroundColor;

  /// {@macro flutter.material.appbar.brightness}
  ///
  /// This property is used to configure an [AppBar].
  final Brightness? brightness;

  /// {@macro flutter.material.appbar.iconTheme}
  ///
  /// This property is used to configure an [AppBar].
  final IconThemeData? iconTheme;

  /// {@macro flutter.material.appbar.actionsIconTheme}
  ///
  /// This property is used to configure an [AppBar].
  final IconThemeData? actionsIconTheme;

  /// {@macro flutter.material.appbar.textTheme}
  ///
  /// This property is used to configure an [AppBar].
  final TextTheme? textTheme;

  /// {@macro flutter.material.appbar.primary}
  ///
  /// This property is used to configure an [AppBar].
  final bool primary;

  /// {@macro flutter.material.appbar.centerTitle}
  ///
  /// This property is used to configure an [AppBar].
  final bool? centerTitle;

  /// {@macro flutter.material.appbar.excludeHeaderSemantics}
  ///
  /// This property is used to configure an [AppBar].
  final bool excludeHeaderSemantics;

  /// {@macro flutter.material.appbar.titleSpacing}
  ///
  /// This property is used to configure an [AppBar].
  final double? titleSpacing;

  /// Defines the height of the app bar when it is collapsed.
  ///
  /// By default, the collapsed height is [toolbarHeight]. If [bottom] widget is
  /// specified, then its height from [PreferredSizeWidget.preferredSize] is
  /// added to the height. If [primary] is true, then the [MediaQuery] top
  /// padding, [EdgeInsets.top] of [MediaQueryData.padding], is added as well.
  ///
  /// If [pinned] and [floating] are true, with [bottom] set, the default
  /// collapsed height is only the height of [PreferredSizeWidget.preferredSize]
  /// with the [MediaQuery] top padding.
  final double? collapsedHeight;

  /// The size of the app bar when it is fully expanded.
  ///
  /// By default, the total height of the toolbar and the bottom widget (if
  /// any). If a [flexibleSpace] widget is specified this height should be big
  /// enough to accommodate whatever that widget contains.
  ///
  /// This does not include the status bar height (which will be automatically
  /// included if [primary] is true).
  final double? expandedHeight;

  /// Whether the app bar should become visible as soon as the user scrolls
  /// towards the app bar.
  ///
  /// Otherwise, the user will need to scroll near the top of the scroll view to
  /// reveal the app bar.
  ///
  /// If [snap] is true then a scroll that exposes the app bar will trigger an
  /// animation that slides the entire app bar into view. Similarly if a scroll
  /// dismisses the app bar, the animation will slide it completely out of view.
  ///
  /// ## Animated Examples
  ///
  /// The following animations show how the app bar changes its scrolling
  /// behavior based on the value of this property.
  ///
  /// * App bar with [floating] set to false:
  ///   {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar.mp4}
  /// * App bar with [floating] set to true:
  ///   {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_floating.mp4}
  ///
  /// See also:
  ///
  ///  * [SliverAppBar] for more animated examples of how this property changes the
  ///    behavior of the app bar in combination with [pinned] and [snap].
  final bool floating;

  /// Whether the app bar should remain visible at the start of the scroll view.
  ///
  /// The app bar can still expand and contract as the user scrolls, but it will
  /// remain visible rather than being scrolled out of view.
  ///
  /// ## Animated Examples
  ///
  /// The following animations show how the app bar changes its scrolling
  /// behavior based on the value of this property.
  ///
  /// * App bar with [pinned] set to false:
  ///   {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar.mp4}
  /// * App bar with [pinned] set to true:
  ///   {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_pinned.mp4}
  ///
  /// See also:
  ///
  ///  * [SliverAppBar] for more animated examples of how this property changes the
  ///    behavior of the app bar in combination with [floating].
  final bool pinned;

  /// {@macro flutter.material.appbar.shape}
  ///
  /// This property is used to configure an [AppBar].
  final ShapeBorder? shape;

  /// If [snap] and [floating] are true then the floating app bar will "snap"
  /// into view.
  ///
  /// If [snap] is true then a scroll that exposes the floating app bar will
  /// trigger an animation that slides the entire app bar into view. Similarly
  /// if a scroll dismisses the app bar, the animation will slide the app bar
  /// completely out of view. Additionally, setting [snap] to true will fully
  /// expand the floating app bar when the framework tries to reveal the
  /// contents of the app bar by calling [RenderObject.showOnScreen]. For
  /// example, when a [TextField] in the floating app bar gains focus, if [snap]
  /// is true, the framework will always fully expand the floating app bar, in
  /// order to reveal the focused [TextField].
  ///
  /// Snapping only applies when the app bar is floating, not when the app bar
  /// appears at the top of its scroll view.
  ///
  /// ## Animated Examples
  ///
  /// The following animations show how the app bar changes its scrolling
  /// behavior based on the value of this property.
  ///
  /// * App bar with [snap] set to false:
  ///   {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_floating.mp4}
  /// * App bar with [snap] set to true:
  ///   {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_floating_snap.mp4}
  ///
  /// See also:
  ///
  ///  * [SliverAppBar] for more animated examples of how this property changes the
  ///    behavior of the app bar in combination with [pinned] and [floating].
  final bool snap;

  /// Whether the app bar should stretch to fill the over-scroll area.
  ///
  /// The app bar can still expand and contract as the user scrolls, but it will
  /// also stretch when the user over-scrolls.
  final bool stretch;

  /// The offset of overscroll required to activate [onStretchTrigger].
  ///
  /// This defaults to 100.0.
  final double stretchTriggerOffset;

  /// The callback function to be executed when a user over-scrolls to the
  /// offset specified by [stretchTriggerOffset].
  final AsyncCallback? onStretchTrigger;

  /// {@macro flutter.material.appbar.toolbarHeight}
  ///
  /// This property is used to configure an [AppBar].
  final double toolbarHeight;

  /// {@macro flutter.material.appbar.leadingWidth}
  ///
  /// This property is used to configure an [AppBar].
  final double? leadingWidth;

  /// {@macro flutter.material.appbar.backwardsCompatibility}
  ///
  /// This property is used to configure an [AppBar].
  final bool backwardsCompatibility;

  /// {@macro flutter.material.appbar.toolbarTextStyle}
  ///
  /// This property is used to configure an [AppBar].
  final TextStyle? toolbarTextStyle;

  /// {@macro flutter.material.appbar.titleTextStyle}
  ///
  /// This property is used to configure an [AppBar].
  final TextStyle? titleTextStyle;

  /// {@macro flutter.material.appbar.systemOverlayStyle}
  ///
  /// This property is used to configure an [AppBar].
  final SystemUiOverlayStyle? systemOverlayStyle;
  @override
  ZoomSliverBarState createState() => ZoomSliverBarState();
}

class ZoomSliverBarState extends State<ZoomSliverBar>
    with TickerProviderStateMixin {
  double _surplusHeight = 0.0;

  late AnimationController _controller;

  late Animation<double> _animation;

  // 超过触发事件距离，触发事件
  _onRefresh() async {
    if (widget.onRefresh == null) return;

    _ZoomSliverBarStatus _zoomSliverBarStatus =
        _ZoomSliverBarStatus.of(context)!;

    _zoomSliverBarStatus.startRefreshs.forEach((f) => f());

    await widget.onRefresh!();

    _zoomSliverBarStatus.stopRefreshs.forEach((f) => f());
  }

  bool _handleScrollEvent(ScrollNotification notification) {
    if (notification.depth != 0 || _controller.isAnimating) {
      return false;
    }

    if (notification is OverscrollNotification &&
        notification.dragDetails != null &&
        notification.dragDetails!.delta.dy > 0) {
      double value = _surplusHeight + notification.dragDetails!.delta.dy;
      if (value <= widget.distance && value >= 0) {
        setState(() {
          _surplusHeight = value;
        });
      }
    }

    if (notification is ScrollEndNotification && _surplusHeight > 0) {
      // 超过触发事件距离，触发事件
      if (_surplusHeight >= widget.triggerDistance) {
        _onRefresh();
      }

      _controller.reset();

      _animation = _controller.drive(Tween<double>(
        begin: _surplusHeight,
        end: 0,
      ));

      _animation.addListener(_setSurplusHeight);

      _controller.forward();
    }

    return true;
  }

  _setSurplusHeight() {
    setState(() {
      _surplusHeight = _animation.value;
    });
  }

  @override
  void initState() {
    super.initState();

    _controller = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 200),
    );

    _controller.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        _animation.removeListener(_setSurplusHeight);
        setState(() {
          _surplusHeight = 0;
        });
      }
    });

    WidgetsBinding.instance!.addPostFrameCallback((_) {
      _ZoomSliverBarStatus.of(context)?.setOnNotification(_handleScrollEvent);
    });
  }

  @override
  Widget build(BuildContext context) {
    return SliverPadding(
      padding: EdgeInsets.zero,
      sliver: SliverAppBar(
        leading: widget.leading,
        automaticallyImplyLeading: widget.automaticallyImplyLeading,
        title: widget.title,
        actions: widget.actions,
        flexibleSpace: widget.flexibleSpace,
        bottom: widget.bottom,
        elevation: widget.elevation,
        shadowColor: widget.shadowColor,
        forceElevated: widget.forceElevated,
        backgroundColor: widget.backgroundColor,
        foregroundColor: widget.foregroundColor,
        brightness: widget.brightness,
        iconTheme: widget.iconTheme,
        actionsIconTheme: widget.actionsIconTheme,
        textTheme: widget.textTheme,
        primary: widget.primary,
        centerTitle: widget.centerTitle,
        excludeHeaderSemantics: widget.excludeHeaderSemantics,
        titleSpacing: widget.titleSpacing,
        collapsedHeight: widget.collapsedHeight,
        expandedHeight: (widget.expandedHeight ?? 0) + _surplusHeight,
        floating: widget.floating,
        pinned: widget.pinned,
        snap: widget.snap,
        stretch: widget.stretch,
        stretchTriggerOffset: widget.stretchTriggerOffset,
        onStretchTrigger: widget.onStretchTrigger,
        shape: widget.shape,
        toolbarHeight: widget.toolbarHeight,
        leadingWidth: widget.leadingWidth,
        backwardsCompatibility: widget.backwardsCompatibility,
        toolbarTextStyle: widget.toolbarTextStyle,
        titleTextStyle: widget.titleTextStyle,
        systemOverlayStyle: widget.systemOverlayStyle,
      ),
    );
  }
}

/// 刷新组件
class ZoomSliverBarRefresh extends StatefulWidget {
  final Widget? child;
  final double size;
  final Color color;
  // 自定义刷新组件
  final Widget? refresh;

  ZoomSliverBarRefresh({
    Key? key,
    this.child,
    this.size = 25,
    this.color = Colors.white,
    this.refresh,
  }) : super(key: key);

  @override
  _ZoomSliverBarRefreshState createState() => _ZoomSliverBarRefreshState();
}

class _ZoomSliverBarRefreshState extends State<ZoomSliverBarRefresh> {
  bool _loading = false;

  Future _startRefresh() async {
    if (_loading) return;

    setState(() {
      _loading = true;
    });
  }

  Future _stopRefresh() async {
    setState(() {
      _loading = false;
    });
  }

  @override
  void initState() {
    super.initState();

    WidgetsBinding.instance!.addPostFrameCallback((_) {
      _ZoomSliverBarStatus? _zoomSliverBarStatus =
          _ZoomSliverBarStatus.of(context);
      _zoomSliverBarStatus?.pushStartRefreshs(_startRefresh);
      _zoomSliverBarStatus?.pushStopRefreshs(_stopRefresh);
    });
  }

  @override
  Widget build(BuildContext context) {
    Widget _child = widget.child != null
        ? widget.child!
        : SizedBox(
            width: widget.size,
            height: widget.size,
          );

    Widget _refresh = widget.refresh != null
        ? widget.refresh!
        : Container(
            width: widget.size,
            height: widget.size,
            child: CircularProgressIndicator(
              valueColor: AlwaysStoppedAnimation<Color>(widget.color),
              strokeWidth: 2,
            ),
          );

    return Stack(
      alignment: Alignment.center,
      children: [
        Opacity(
          opacity: _loading ? 0 : 1,
          child: _child,
        ),
        _loading ? _refresh : SizedBox(),
      ],
    );
  }
}
